<?php

namespace app\api\service;

use app\common\controller\Send;
use think\facade\Log;

class Pay
{
    use Send;

    // 小程序app_id
    protected $appId;
    // 小程序app_secret
    protected $appSecret;
    // 商户号id
    protected $mchId;
    // 申请支付后有给予一个商户账号和密码，登陆后自己设置的key
    protected $apiKey;
    protected $apiUrlPre = 'https://api.mch.weixin.qq.com';

    public function __construct()
    {
        $this->appId = config('app.wx.app_id');
        $this->appSecret = config('app.wx.app_secret');
        $this->mchId = config('app.wx_pay.mch_id');
        $this->apiKey = config('app.wx_pay.api_key');
    }

    /**
     * 统一下单
     * https://pay.weixin.qq.com/wiki/doc/api/wxa/wxa_api.php?chapter=9_1&index=1
     * @param $openid
     * @param $fee
     * @param $body
     * @return array
     */
    public function unifiedOrder($openid, $fee, $body)
    {
        // 小程序ID
        $appId = $this->appId;
        // 商户号ID
        $mchId = $this->mchId;
        // 随机字符串
        $nonceStr = $this->nonceStr();
        // 附加数据
        $attach = '广州博学教育';
        // 商户订单号
        $outTradeNo = $this->orderNumber($openid);
        // 标价金额，订单总金额，单位为分，所以要*100
        $totalFee = $fee * 100;
        // 服务器的ip
        $spBillCreateIp = request()->ip;
        // 回调的url，异步接收微信支付结果通知的回调地址，通知url必须为外网可访问的url，不能携带参数。
        $notifyUrl = url('/pay/notify', '', true, true);
        // 交易类型 默认
        $tradeType = 'JSAPI';

        // 生成签名的数据
        $post = [
            'appid' => $appId,
            'attach' => $attach,
            'body' => $body,
            'mch_id' => $mchId,
            'nonce_str' => $nonceStr,
            'notify_url' => $notifyUrl,
            'openid' => $openid,
            'out_trade_no' => $outTradeNo,
            'spbill_create_ip' => $spBillCreateIp,
            'total_fee' => $totalFee,
            'trade_type' => $tradeType,
        ];

        // 生成签名
        $post['sign'] = $this->sign($post);
        // 转成xml格式
        $xml = $this->toXml($post);

        // 请求统一下单接口prepay_id
        $url = $this->apiUrlPre . '/pay/unifiedorder';
        $xml = curl_post($url, $xml);

        // xml转成array
        $array = $this->fromXml($xml);

        if ($array['return_code'] == 'SUCCESS' && $array['result_code'] == 'SUCCESS') {
            $time = time();
            // 数据包,统一下单接口返回的 prepay_id 参数值，提交格式如：prepay_id=wx2017033010242291fcfe0db70013231072
            $package = 'prepay_id=' . $array['prepay_id'];
            $data = [
                'timeStamp' => $time,
                'nonceStr' => $nonceStr,
                'package' => $package,
                'signType' => 'MD5',
                'paySign' => $this->sign([
                    'appId' => $appId,
                    'nonceStr' => $nonceStr,
                    'package' => $package,
                    'signType' => 'MD5',
                    'timeStamp' => $time
                ]),
                'outTradeNo' => $outTradeNo,
            ];

            return self::sendResult(self::$okCode, $data);
        } else {
            $data = [
                'text' => '错误',
                'return_code' => $array['return_code'],
                'return_msg' => $array['return_msg'],
                'err_code_des' => $array['err_code_des'],
            ];
            // 实时写入报错信息
            Log::write($array, 'wxPayUnifiedOrder');

            return self::sendResult(121, $data);
        }
    }

    /**
     * 随机32位字符串
     * @param int $length
     * @return string
     */
    private function nonceStr($length = 32)
    {
        $result = '';
        $str = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
        for ($i = 0; $i < $length; $i++) {
            $result .= $str[rand(0, strlen($str) - 1)];
        }
        return $result;
    }

    /**
     * 生成订单号
     * @param $openid
     * @return string
     */
    private function orderNumber($openid)
    {
        return md5($openid . time() . rand(10, 99));//32位
    }

    /**
     * 生成签名
     * @param $data
     * @return string
     */
    private function sign($data)
    {
        // 签名步骤一：对参数按照key=value的格式，并按照参数名ASCII字典序排序
        ksort($data);
        $string = $this->toUrlParams($data);
        // 签名步骤二：在string后加入API密钥（为商户平台设置的密钥key）
        $string = $string . "&key=" . $this->apiKey;
        // 签名步骤三：MD5加密
        $string = md5($string);
        // 签名步骤四：所有字符转为大写
        $result = strtoupper($string);
        return $result;
    }

    /**
     * 格式化参数格式化成url参数
     * @param $data
     * @return string
     */
    private function toUrlParams($data)
    {
        $string = '';
        foreach ($data as $key => $value) {
            if (!$value) {
                continue;
            }

            if ($string) {
                $string .= '&' . $key . "=" . $value;
            } else {
                $string = $key . "=" . $value;
            }
        }
        return $string;
    }

    /**
     * 输出xml字符
     * @param $data
     * @return string
     */
    private function toXml($data)
    {
        if (!is_array($data) || count($data) <= 0) {
            return '';
        }

        $xml = "<xml>";
        foreach ($data as $key => $val) {
            if (is_numeric($val)) {
                $xml .= "<" . $key . ">" . $val . "</" . $key . ">";
            } else {
                // CDATA标签用于说明数据不被XML解析器解析。
                $xml .= "<" . $key . "><![CDATA[" . $val . "]]></" . $key . ">";
            }
        }
        $xml .= "</xml>";
        return $xml;
    }

    /**
     * 将xml转为array
     * @param $xml
     * @return array|mixed
     */
    public function fromXml($xml)
    {
        if (!$xml) {
            return [];
        }
        //禁止引用外部xml实体
        libxml_disable_entity_loader(true);
        return json_decode(json_encode(simplexml_load_string($xml, 'SimpleXMLElement', LIBXML_NOCDATA)), true);
    }
}